pub mod constant;
pub mod fan;
pub use fan::*;

use crate::xueliuhz::constant::{
    MJFanXing, MJFanXing::*, FAN_FUNC, FAN_MATRIX, FAN_PATTERN, ORDER_FAN,
};
use lazy_static::lazy_static;
use log::info;
use mahjong_lib::*;
use std::{
    collections::{hash_map::DefaultHasher, HashMap},
    fmt,
    hash::{Hash, Hasher},
    time::{Duration, Instant},
};

pub struct FanData {
    pub hutile: i8,
    pub fancount: u16,
    pub fandata: Vec<MJFanXing>,
}

#[inline]
fn abuf(hbuf: &mut [u8; 53], idx: &mut usize, v: u8) {
    hbuf[*idx] = v;
    *idx += 1;
}

#[repr(C)]
#[derive(Clone, Copy)]
pub union MJRet {
    pub i: [u8; 8],
    pub f: u64,
}

impl fmt::Debug for MJRet {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        unsafe { write!(f, "MJRet: {:#x}", self.f) }
    }
}

impl MJRet {
    fn v2ret(&mut self, vs: &[u8], ret: u8) -> u64 {
        unsafe {
            self.f = 0;
            self.i[0] = ret;
            for (n, v) in vs.iter().enumerate() {
                self.i[n + 1] = *v;
            }
            self.f
        }
    }

    fn ret2v(&self, vs: &mut Vec<u8>) {
        unsafe {
            for i in 0..7 {
                vs.push(self.i[i + 1]);
            }
        }
    }
}

#[repr(C)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum GangType {
    AN,
    BU,
    DIAN,
}

#[repr(C)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum CPGType {
    CHI,
    PENG,
    GANG,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CPG {
    pub cpg_type: CPGType,
    pub gang_type: GangType,
    pub tile: u8,
    pub from_seat: u8,
    pub com: [u8; 4],
}

#[derive(PartialEq, Debug, Clone, Copy)]
#[repr(u8)]
pub enum HuType {
    ZIMO,
    DIANPAO,
    UNKNOWN,
}

#[derive(Debug, Clone)]
pub struct XlhzTiles {
    pub que: TileType,
    pub cpg: Vec<CPG>,
    pub hutype: HuType,
    pub now_tile: u8,
    pub tile_pool: Vec<u8>,
    pub ti_hand: MJTiles,
    pub ti_hand_now: MJTiles,
    pub ti_all: MJTiles,
    pub ti_gang4to3: MJTiles,
}

impl XlhzTiles {
    pub fn new() -> Self {
        Self {
            que: TileType::INVAILD,
            hutype: HuType::UNKNOWN,
            now_tile: 0,
            cpg: vec![],
            tile_pool: vec![],
            ti_hand: MJTiles::new(),
            ti_hand_now: MJTiles::new(),
            ti_all: MJTiles::new(),
            ti_gang4to3: MJTiles::new(),
        }
    }

    pub fn assign(
        &mut self,
        vhand: &[u8],
        now_tile: u8,
        que: TileType,
        vcpg: &[CPG],
        ht: HuType,
    ) -> Result<(), String> {
        if vhand.len() > 14 || vhand.len() < 1 {
            return Err(String::from("invaild hand length"));
        }
        if now_tile > 35 {
            return Err(String::from("invaild now_tile(>35)"));
        }
        let mut vnow = Vec::new();
        vnow.extend_from_slice(vhand);
        if now_tile != 0 {
            vnow.push(now_tile);
        }
        let vlz = vec![35u8];
        self.ti_hand.assign(vhand, &vlz);
        self.ti_hand_now.assign(&vnow, &vlz);
        let mut v43 = Vec::new();
        v43.extend_from_slice(&vnow);

        for v in vcpg {
            match v.cpg_type {
                CPGType::CHI => {}
                CPGType::PENG => {
                    for i in 0..3 {
                        vnow.push(v.tile);
                        v43.push(v.tile);
                    }
                }
                CPGType::GANG => {
                    for i in 0..3 {
                        vnow.push(v.tile);
                        v43.push(v.tile);
                    }
                    vnow.push(v.tile);
                }
            }
            self.cpg.push(*v);
        }
        if vnow.len() > 14 {
            return Err(String::from("invaild all length(>14)"));
        }
        self.ti_all.assign(&vnow, &vlz);
        self.ti_gang4to3.assign(&v43, &vlz);

        self.now_tile = now_tile;
        self.que = que;
        self.hutype = ht;

        Ok(())
    }
}

pub struct XlhzCalFx {
    pub hongzhong_count: u8,
    pub hu_result: HashMap<u8, Vec<FanData>>,
    pub csx_raw: XlhzTiles,
    pub csx_cal: XlhzTiles,
}

impl Hash for XlhzCalFx {
    fn hash<H: Hasher>(&self, state: &mut H) {
        let mut hash_buf = [0u8; 53];
        let mut i: usize = 0;
        abuf(&mut hash_buf, &mut i, self.csx_raw.now_tile);
        abuf(&mut hash_buf, &mut i, self.csx_raw.ti_hand.tiles_len);
        let mut hands: Vec<u8> = vec![];
        self.csx_raw.ti_hand.get_hands(&mut hands, 255);
        hands.sort();
        for n in 0..14 {
            let mut t = 0u8;
            if n < self.csx_raw.ti_hand.tiles_len {
                t = hands[n as usize];
            }
            abuf(&mut hash_buf, &mut i, t);
        }
        abuf(&mut hash_buf, &mut i, 35);
        abuf(&mut hash_buf, &mut i, self.csx_raw.que as u8);
        abuf(&mut hash_buf, &mut i, self.csx_raw.cpg.len() as u8);
        for n in 0..4 {
            let vaild = n + 1 <= self.csx_raw.cpg.len();
            // 整理8个整数, 缺省为全0
            let mut v8 = [0u8; 8];
            if vaild {
                let cc = &self.csx_raw.cpg[n];
                match cc.cpg_type {
                    CPGType::CHI => {
                        v8[0] = cc.tile;
                        for j in 0..4 {
                            v8[j + 1] = cc.com[j];
                        }
                        v8[5] = cc.from_seat;
                    }
                    CPGType::PENG => {
                        v8[0] = cc.tile;
                        v8[5] = cc.from_seat;
                        v8[6] = 1;
                    }
                    CPGType::GANG => {
                        v8[0] = cc.tile;
                        v8[5] = cc.from_seat;
                        v8[6] = 2;
                        v8[7] = cc.gang_type as u8;
                    }
                }
            }
            for j in 0..8 {
                abuf(&mut hash_buf, &mut i, v8[j]);
            }
        }
        abuf(&mut hash_buf, &mut i, self.hongzhong_count as u8);
        abuf(&mut hash_buf, &mut i, self.csx_raw.hutype as u8);
        info!("hash_buf...{:?}", hash_buf);
        hash_buf.hash(state);
    }
}

impl XlhzCalFx {
    fn has_ke_fan(&self, f: MJFanXing) -> bool {
        matches!(
            f,
            MJFanXing::SIANKE
                | MJFanXing::SANANKE
                | MJFanXing::SHUANGANKE
                | MJFanXing::SANJIEGAO
                | MJFanXing::SIJIEGAO
        )
    }

    fn can_rm_all_ke(&self, f: MJFanXing) -> bool {
        matches!(
            f,
            MJFanXing::ZHUOWUKUI
                | MJFanXing::SHUANGTONGKE
                | MJFanXing::LAOSHAOPEI
                | MJFanXing::DANDIAO
                | MJFanXing::YIBANGAO
                | MJFanXing::LIULIANSHUN
                | MJFanXing::MENQING
                | MJFanXing::BIANZHANG
                | MJFanXing::KANZHANG
        )
    }

    fn get_que_tiles(&self) -> Vec<u8> {
        let mut q = Vec::new();
        let c = &self.csx_cal.ti_hand_now[self.csx_cal.que];
        for (i, &cnt) in c.bucket.iter().enumerate() {
            for _ in 0..cnt {
                q.push((i as u8) + 1 + self.csx_cal.que as u8 * 10);
            }
        }
        q
    }

    fn get_left_tiles(&self) -> Vec<u8> {
        match self.csx_cal.que as usize {
            0 => vec![
                11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 35,
            ],
            1 => vec![
                1, 2, 3, 4, 5, 6, 7, 8, 9, 21, 22, 23, 24, 25, 26, 27, 28, 29, 35,
            ],
            2 => vec![
                1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 35,
            ],
            _ => vec![],
        }
    }

    fn get_ting_tiles_uniq(&self) -> Vec<u8> {
        let q = self.get_que_tiles();
        if q.len() == 1 {
            q
        } else {
            let mut t = Vec::new();
            if q.is_empty() {
                for n in 0..5 {
                    let c = &self.csx_cal.ti_hand_now.counters[n];
                    for (i, &cnt) in c.bucket.iter().enumerate() {
                        if cnt > 0 {
                            t.push((i as u8) + 1 + if n == 4 { 34 } else { n as u8 * 10 });
                        }
                    }
                }
            }
            t
        }
    }

    pub fn check_ke_tiles(
        &self,
        mfi: &mut XlhzTiles,
        fan: MJFanXing,
        ketiles: &mut Vec<u8>,
        ret_data: u64,
        start: u8,
        rmove: bool,
    ) -> u64 {
        if !self.has_ke_fan(fan) {
            return ret_data;
        }

        let mut mr = MJRet { f: 0 };
        mr.f = ret_data;
        let mut retv: Vec<u8> = vec![];
        mr.ret2v(&mut retv);
        if start == 1 {
            ketiles.extend(retv.iter());
            return ret_data;
        }

        let mut remain_ke: Vec<u8> = retv.into_iter().filter(|r| !ketiles.contains(r)).collect();
        if remain_ke.is_empty() {
            return ret_data;
        }

        let mut all_ke = if fan == MJFanXing::SHUANGANKE && rmove {
            remain_ke.clone()
        } else {
            let mut all_ke = ketiles.clone();
            all_ke.extend(remain_ke.iter());
            all_ke
        };

        let cpg_tiles: Vec<u8> = mfi.cpg.iter().map(|cpg| cpg.tile).collect();
        let cpremainke: Vec<u8> = all_ke
            .into_iter()
            .filter(|tile| !cpg_tiles.contains(tile))
            .collect();

        if cpremainke.is_empty() {
            ketiles.extend(remain_ke.iter());
            return ret_data;
        }

        let mut cptiles = vec![];
        mfi.ti_hand.get_hands(&mut cptiles, 0);
        for ke in cpremainke.iter() {
            for _ in 0..3 {
                if hand_nt(&mut cptiles, &mut mfi.now_tile, *ke) == 0 {
                    return 0;
                }
            }
        }

        let ret = has_hu(&cptiles, mfi.now_tile);
        if ret {
            ketiles.extend(remain_ke.iter());
            return ret_data;
        }
        0
    }

    fn calc_fan_count(
        &mut self,
        in_tile: u8,
        ts: Vec<u8>,
        h: HuType,
        _seat: i8,
        mjf: &mut Vec<FanData>,
    ) {
        let lz = vec![35];
        self.csx_cal
            .assign(&ts, in_tile, self.csx_raw.que, &self.csx_raw.cpg, h);

        let mut mfv = Vec::new();
        let mut mfd = HashMap::new();
        let mut mfc = 0;

        self.get_max_fan_data(/*&mut self.csx_cal, */ &mut mfv, &mut mfd, &mut mfc);

        let mf = FanData {
            hutile: in_tile as i8,
            fancount: mfc as u16,
            fandata: mfv,
        };

        mjf.push(mf);
    }

    fn has_ting(&mut self) -> u32 {
        let start_time = Instant::now();

        let tingv = self.get_ting_tiles_uniq();
        let tlen = tingv.len();
        if tlen == 0 {
            return u32::MAX; // -1 in unsigned representation
        }

        let leftv = self.get_left_tiles();

        // Clear hu_result
        self.hu_result.clear();

        for &out_tile in &tingv {
            let mut ts = Vec::new();
            self.csx_raw.ti_hand_now.get_hands(&mut ts, out_tile);

            let mut res = Vec::new();
            for n in &leftv {
                if has_hu(&ts, *n) {
                    res.push(n);
                }
            }
            if res.is_empty() {
                continue;
            }

            let mut mjfans = Vec::new();
            for &in_tile in &res {
                // Clone ts to avoid mutation by calc_fan_count
                let ts_clone = ts.clone();
                // self.calc_fan_count(in_tile, &ts_clone, self.csx_raw.hutype, -1, &mut mjfans);
            }
            self.hu_result.insert(out_tile, mjfans);
        }

        let duration = start_time.elapsed();
        duration.as_micros() as u32
    }

    fn get_current_fan_count(
        &self,
        fan_data: &Vec<MJFanXing>,
        gen_count: u64,
        fan_dict: &mut HashMap<MJFanXing, i32>,
        fan_count: &mut i32,
    ) {
        fan_dict.clear();
        *fan_count = 1;
        for &fan in fan_data {
            if fan == MJFanXing::HUJIAOZHUANYI {
                fan_dict.insert(fan, 1);
                continue;
            }
            let value = match FAN_PATTERN.get(&fan) {
                Some(detail) => detail.value,
                None => 0,
            };
            let value = if value == 0 {
                (2i32.pow(gen_count as u32)) as i32
            } else {
                value
            };
            fan_dict.insert(fan, value);
            *fan_count *= value;
        }
    }

    fn get_max_fan_data(
        &self,
        // mfi: &mut XlhzTiles,
        max_fan_data: &mut Vec<MJFanXing>,
        max_fan_dict: &mut HashMap<MJFanXing, i32>,
        max_fan_count: &mut i32,
    ) {
        let mut fdata = Vec::new();
        self.get_fan_xing(&mut fdata);
        *max_fan_count = 0;

        for fd in fdata {
            let mut gen = 0;
            let mut isbreak = false;

            for &f in &fd {
                if matches!(
                    f,
                    MJFanXing::LONGQIDUI
                        | MJFanXing::SHUANGLONGQIDUI
                        | MJFanXing::SANLONGQIDUI
                        | MJFanXing::SHIBALUOHAN
                        | MJFanXing::SHIERJINCHAI
                ) {
                    isbreak = true;
                    break;
                }
            }

            let mut fd = fd;
            if !isbreak {
                gen = fx_fan_gen_count(&self.csx_cal); // 假设 get_fan_gen_count 已经定义
                if gen >= 1 {
                    fd.push(MJFanXing::GEN);
                }
            }

            let mut fan_dict = HashMap::new();
            let mut fan_count = 0;
            self.get_current_fan_count(&fd, gen, &mut fan_dict, &mut fan_count);

            if fan_count > *max_fan_count {
                *max_fan_data = fd;
                *max_fan_dict = fan_dict;
                *max_fan_count = fan_count;
            }
        }
    }

    fn init_basic_fan_types(&self, all_fan: &mut HashMap<MJFanXing, u64>) {
        let tile = self.csx_cal.now_tile;
        let tins = self.csx_cal.ti_hand.find_tile(tile); // 假设 find_tile 方法已定义
        let qidui = fx_qi_dui(&self.csx_cal); // 假设 fx_qi_dui 方法已定义

        let is_qidui = (tile == 35 || tins) && qidui > 0;
        all_fan.insert(MJFanXing::QIDUI, (is_qidui && qidui == 1) as u64);
        all_fan.insert(MJFanXing::LONGQIDUI, (is_qidui && qidui == 10) as u64);
        all_fan.insert(MJFanXing::SHUANGLONGQIDUI, (is_qidui && qidui == 20) as u64);
        all_fan.insert(MJFanXing::SANLONGQIDUI, (is_qidui && qidui == 30) as u64);

        all_fan.insert(
            MJFanXing::BUQIUREN,
            (fx_men_qing(&self.csx_cal) != 0 && self.csx_cal.hutype == HuType::ZIMO) as u64,
        );
        all_fan.insert(
            MJFanXing::MENQING,
            (fx_men_qing(&self.csx_cal) != 0 && self.csx_cal.hutype != HuType::ZIMO) as u64,
        );
        all_fan.insert(MJFanXing::DUANYAOJIU, fx_duan_yao_jiu(&self.csx_cal) as u64);
        all_fan.insert(MJFanXing::SUHU, (!self.csx_cal.ti_all.find_tile(35)) as u64);

        // 其他基本番型的初始化 ...
    }

    pub fn get_fan_xing(&self, fan_datas: &mut Vec<Vec<MJFanXing>>) {
        let mut all_fan = HashMap::new();
        // 初始化基本番型条件
        self.init_basic_fan_types(&mut all_fan);

        for &fan in ORDER_FAN.iter() {
            let mut cp_mfi = self.csx_cal.clone();
            let mut cp_fan = all_fan.clone();
            let mut ketiles: Vec<u8> = Vec::new();
            let mut fan_data = Vec::new();
            let mut remove = false;

            if cp_fan.get(&fan).is_none() {
                // let ret = self.calculate_fan_type(&fan, &mut cp_mfi);
                if let Some(ff) = FAN_FUNC.get(&fan) {
                    let ret = ff(&mut cp_mfi);
                    cp_fan.insert(fan, ret);
                    if ret > 0 {
                        fan_data.push(fan);
                    } else {
                        continue;
                    }
                }
            } else if cp_fan[&fan] > 0 {
                fan_data.push(fan);
            } else {
                continue;
            }

            if let Some(relations) = FAN_MATRIX.get(&fan) {
                for &ex_fan in relations {
                    if self.can_rm_all_ke(ex_fan) && !remove {
                        rm_ke_tiles(&mut cp_mfi, &ketiles);
                        remove = true;
                    }
                    if let Some(ff) = FAN_FUNC.get(&ex_fan) {
                        let ret = ff(&mut cp_mfi);
                        if ret > 0 {
                            cp_fan.insert(ex_fan, ret);
                            fan_data.push(ex_fan);
                        }
                    }
                }
            }

            fan_datas.push(fan_data);
        }

        if fan_datas.is_empty() {
            fan_datas.push(vec![MJFanXing::PINGHU]);
        }
    }
}
